#include <numeric>
#include "checkers/AlignCacheline.h"

//static llvm::cl::OptionCategory ToolingSampleCategory("AlignCacheline");

extern ReportSpace::Report external_bug_report;

void AlignCacheline::check() {
  readConfig();
  getStucture();
  //RWAnalyzer rwAnalyzer(manager, resource);
}

void AlignCacheline::readConfig() {
    std::unordered_map<std::string, std::string> ptrConfig =
            configure->getOptionBlock("AlignCachelineChecker");
    cacheline_size = stoi(ptrConfig.find("cacheline_size")->second);
    DefaultLoopCount = stoi(ptrConfig.find("DefaultLoopCount")->second);
    frequent_access_threshold = stoi(ptrConfig.find("frequent_access_threshold")->second);
    avg_dev_threshold = stof(ptrConfig.find("avg_dev_threshold")->second);
}

bool AlignCacheline::analyzeRWPosCheckFailed(RecordDecl *decl, const RWAccess *rwAccess) {
    auto &writeArray = rwAccess->writeArray;
    auto &readArray = rwAccess->readArray;
    const clang::ASTRecordLayout &typeLayout(decl->getASTContext().getASTRecordLayout(decl));
    int size = typeLayout.getSize().getQuantity();

    std::vector<int> totalArray;
    for (int i = 0; i < writeArray.size(); i++) {
        totalArray.push_back(writeArray[i] + readArray[i]);
    }
    // pass the check if the absolute deviation is less than threshold.
    double sum = std::accumulate(std::begin(totalArray), std::end(totalArray), 0.0);
    double mean =  sum / totalArray.size(); //mean
    double accum  = 0.0;
    std::for_each (std::begin(totalArray), std::end(totalArray), [&](const double d) {
        accum  += abs(d-mean);
    });
    double avgDev = accum/(totalArray.size()-1); //absolute deviation
    if (avgDev < avg_dev_threshold)
        return  false;

    // report bug if frequent accesses cross multiple cache lines.
    int count = 0;
    std::vector<unsigned int> freCount(size/cacheline_size + 1, 0);
    for (FieldDecl *fd : decl->fields()) {
        if (totalArray[count] > frequent_access_threshold) {
            unsigned int inByte = typeLayout.getFieldOffset(count)/8;
            freCount[inByte/cacheline_size] = 1;
        }
        count++;
    }
    int  freSum = std::accumulate(std::begin(freCount), std::end(freCount), 0);
    if (freSum <= 1)
        return false;
    return true;
};

void AlignCacheline::analyzeBug(RecordDecl *decl, const RWAccess *rwAccess) {
    string desc = "";
    analyzeRWPosCheckFailed(decl, rwAccess);
    auto hasAlignedAttr = [](const RecordDecl* D) {
        for (Attr* attr : D->attrs()) {
            if (attr->getKind() == clang::attr::Aligned) {
                return true;
            }
        }
        return false;
    };
    // Only report aligned attribute issue when a strucutre may cross multiple cacheline.
    if (!hasAlignedAttr(decl) ) {
        desc += "record " + decl->getQualifiedNameAsString() + " needs aligned attribute.";
    }
    if (analyzeRWPosCheckFailed(decl, rwAccess)) {
        desc += "Frequent accessed elements in record " + decl->getQualifiedNameAsString() + " cross multiple cache lines.";
    }
    if (desc == "")
        return;

    const clang::SourceManager &SM = decl->getASTContext().getSourceManager();
    SourceLocation SL = decl->getBeginLoc();
    unsigned int lineNum = SM.getExpansionLineNumber(SL);
    unsigned int columnNum = SM.getExpansionColumnNumber(SL);
    string filename = SL.printToString(SM);
    ReportSpace::Defect *def = new ReportSpace::Defect(ReportSpace::DefectName::AlignCacheline,
                                                       ReportSpace::DefectType::Error,
                                                       filename, lineNum, columnNum,
                                                       decl->getQualifiedNameAsString());
    def->addDesc(desc);
    external_bug_report.addToReport(def);
}

void AlignCacheline::getStucture() {
    std::vector<ASTFile *> astFiles = resource->getASTFiles();
    int count = 1;
    for(ASTFile * astFile : astFiles)
    { 
        process_bar(float(count++) / astFiles.size());
        ASTUnit * astUnit = manager->getASTUnit(astFile);
        ASTContext& AST = astUnit->getASTContext();
        StructureAnalyzer analyzer(&AST, configure);
        analyzer.setDefaultLoopCount(DefaultLoopCount);
        analyzer.analyze();
        auto &frequentVar = analyzer.retFrequentVar();
        for (auto pair : frequentVar) {
            const VarDecl* D = pair.second;
            auto declType = D->getType();
            if (!declType->isRecordType()) {
                continue;
            }
            RecordDecl * decl = declType->getAsRecordDecl();
            analyzeBug(decl, pair.first);
        }
    }
}
